package stv5.managers;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.apache.xerces.parsers.DOMParser;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import stv5.Episode;
import stv5.Request;
import stv5.Series;
import stv5.SeriesList;
import stv5.abstracted.AbstractManager;
import stv5.abstracted.EpisodeManager;

/**
 * The "new" MediaTombManager
 * @author dhleong
 *
 */
public class MediaTombManager extends AbstractManager implements EpisodeManager {
		
	private static final int SID_WAIT = 5000;
	private static final int UPDATE_WAIT = 2500;
	
	private String broadIp, trProfile;
	private int mtPort;
	private boolean trCheck = false;
	private List<String> trExtensions;
	private HashMap<Integer, ArrayList<Episode>> episodes;
	public String[] localFolders;
	
	private String sid;
	private SeriesList mySeries;

	public MediaTombManager(String broadcastingIp, int mtport, String trProf, 
			boolean trCheck, List<String> trExtensions) {
		this.broadIp = broadcastingIp;
		this.mtPort = mtport;
		this.trProfile = trProf;
		this.trCheck = trCheck;
		this.trExtensions = trExtensions;
	}

	@Override
	public List<Episode> getEpisodes(int seriesId) {
		return episodes.get(seriesId);
	}

	@Override
	public String getLocalForEpisode(Series s, Episode e) {
		Series mine = mySeries.getByName(s);
		if (mine != null && mine.getLocalPath() != null)
			return mine.getLocalPath() + File.separator + e.getName();
		
		return null;
	}

	@Override
	public void handleRequest(StringBuffer buff, Request request) {
		// nothing to do
	}
	
	/**
	 * @return True if the MediaTomb is updating its database 
	 */
	public boolean isUpdating() {
		try {
			Document doc = loadPage("update&force_update=0");
			NodeList tasklist = doc.getElementsByTagName("task");
			return (tasklist.getLength() > 0);			
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
		}
		return false;
	}

	@Override
	public void manageSeries(Series s) {		
		Series original = mySeries.getByName(s);
		if (original != null) {
			original.manageIfy(s);			
			
			// load episodes directly		
			episodes.put(original.getId(), 
					episodesForContainer(original.getGuid(), original.getLocalPath())
			);
		} else mySeries.add(s);		
	}

	@Override
	public void reload() {
		episodes = new HashMap<Integer, ArrayList<Episode>>();
		
		// get a new SID
		if (!refreshSID()) {
			System.out.println (" * SID Refresh, attempt #2 in ~5 sec");
			if (doSleep(SID_WAIT) && !refreshSID()) {
				System.out.println(" * SID Refresh, attempt #3 in ~5 sec");
				if (doSleep(SID_WAIT) && !refreshSID()) {
					System.out.println(" * Failed to refresh SID after 3 attempts!");
					System.out.println(" * You may want to hard-restart STv5!");
				}
			}
		}
		
		// find local folders from the "autoscan" list
		try {
			Document localdoc = loadPage("autoscan&action=list");
			NodeList loclist = localdoc.getElementsByTagName("location");
			localFolders = new String[ loclist.getLength() ];
			for (int i=0; i<loclist.getLength(); i++) 
				localFolders[i] = loclist.item(i).getTextContent();				
			
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		// wait until it's done updating
		if (isUpdating()) {
			System.out.print(" - Waiting for MediaTomb to finish updating");
			do {
				doSleep(UPDATE_WAIT);
				System.out.print(".");
			} while (isUpdating());
			System.out.println(" Done.");
		}
		
		mySeries = spiderFolders();
	}

	@Override
	public int size() {
		return episodes.size();
	}
	
	/**
	 * @return All possible series
	 */
	public SeriesList spiderFolders() {
		SeriesList ret = new SeriesList();
		doSpiderFolders("1", "", ret);
		return ret;
	}
	@Override
	public SeriesList spiderFolders(String folderName) {
		// our strategy for this, since MT operates
		//	with ids and not folder names, is to spider
		// 	everything and remove those without folderName
		SeriesList ret = spiderFolders();
		Iterator<Series> iter = ret.iterator();
		Series curr;
		while (iter.hasNext()) {
			curr = iter.next();
			if (!curr.getLocalPath().contains(folderName))
				iter.remove();
		}
		return ret;
	}
	
	private void doSpiderFolders(String containerId, String currentName, SeriesList ret) {
		ArrayList<Episode> currEpisodes;
		
		currEpisodes = episodesForContainer(containerId, currentName);
		if (!currEpisodes.isEmpty()) {
			String[] tmp = currentName.split( File.separator );
			Series news = new Series( capitalizeWords(tmp[ tmp.length-1 ]), containerId);
			news.setLocalPath(currentName);
			ret.add( news );
		}
		
		try {
			// recurse into subdirectories
			Document cont = loadPage("containers&parent_id="+containerId);
			NodeList nodes = cont.getElementsByTagName("container");
			Node curr;
            for (int i=0; i<nodes.getLength(); i++) {
            	curr = nodes.item(i);

        		// only extend the series name if this one has episodes in it
        		String newname = currentName + File.separator + curr.getTextContent();        			
        			
        		// recurse
        		doSpiderFolders(
        				curr.getAttributes().getNamedItem("id").getNodeValue(), 
        				newname, 
        				ret
        		);            	
            }
		} catch (SAXException e) {
		} catch (IOException e) {
		}
	}
	

	private boolean doSleep(int millis) {
		try {
			Thread.sleep(millis);
		} catch (InterruptedException e) {}
		
		return true;
	}
	
	/**
	 * @param containerId
	 * @param currentName 
	 * @return empty list on failure
	 */
	private ArrayList<Episode> episodesForContainer(String containerId, String localPath) {
		ArrayList<Episode> episodes = new ArrayList<Episode>();
		try {			
			Document cont = loadPage("items&parent_id="+containerId);
			NodeList items = cont.getElementsByTagName("item");
			if (items == null || items.getLength() == 0)
				return episodes;
			
			boolean transcode = true;
			NodeList children;
			String link, title, ext;	
			File currentDir = new File(localPath);
			File[] matchedFiles;
			SubtitleMatchingFilter filter;
			for (int i=0; i<items.getLength(); i++) {
				children = items.item(i).getChildNodes();
				// "title" (filename) is first child
				title = children.item(0).getTextContent();
				if (isVideoFile(title)) {
					// "res" (the link) is second child
					link = children.item(1).getTextContent();
					
					// (possibly) check for subtitles before transcoding
					if (trCheck && currentDir != null) {
						ext = title.substring( title.lastIndexOf('.')+1 );
						if (!trExtensions.contains(ext)) {
							// only bother with this if we don't already
							//	know to force transcoding by the filename
							filter = new SubtitleMatchingFilter(title);
							matchedFiles = currentDir.listFiles(filter);
							if (matchedFiles != null && matchedFiles.length == 0) 							
								transcode = false;
							matchedFiles = null;
						}
					}
					
					if (transcode) {
						// only make the link transcode if necessary
						link = link.replace("res_id=0", "res_id=none") + 
							"&pr_name="+trProfile+"&tr=1";
					}
					
					transcode = true; // reset for next time
					episodes.add(new Episode(title, link));					
				}
			}
		} catch (SAXException e) {
		} catch (IOException e) {
		}
		
		Collections.sort(episodes);
		return episodes;
	}
	
	/**
	 * @param url, starting with the req_type=
	 * 	eg: "containers&parent_id=0"
	 * @return
	 * @throws SAXException
	 * @throws IOException
	 */
	private Document loadPage(String url) throws SAXException, IOException {		
		DOMParser parser = new DOMParser();
		parser.parse("http://"+broadIp+":"+mtPort+"/content/interface?sid="+sid+"&req_type="+url);
		Document doc = parser.getDocument();
		return doc;
	}
	
	/**
	 * @return True on success, else false
	 */
	private boolean refreshSID() {
		// get an sid for mediatomb
		try {
			Document siddoc = loadPage("auth&checkSID=1");
			NodeList rootlist = siddoc.getElementsByTagName("root");
			Node rootNode = rootlist.item(0);			
			sid = rootNode.getAttributes().getNamedItem("sid").getNodeValue();			
		} catch (SAXException e) {
			e.printStackTrace();
			return false;
		} catch (IOException e) {
			//e.printStackTrace();
			System.out.println("Could not connect to mediatomb server. Failed.");
			System.exit(1);		
			return false;
		} catch (NullPointerException e) {
			System.out.println(" * Could not retrieve a new SID.");
			return false;
		}
		
		return true;
	}
	
	private class SubtitleMatchingFilter implements FilenameFilter {
		
		private String original;
		
		public SubtitleMatchingFilter(String original) {
			this.original = original.substring(0, original.lastIndexOf('.'));
		}

		@Override
		public boolean accept(File dir, String name) {
			return name.startsWith(original) && AbstractManager.isSubtitleFile(name);
		}
		
	}
}
